Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[week1] 1차 세미나 정규 과제 #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Conversation

seokbeom00
Copy link
Contributor

프로젝트 구조

image

Data

Member

package Data;

import java.util.Date;

public class Member {
    private Long id;
    private String name;
    private Date enterDate;
    private String birth;
    private int account;
    private int balance;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBirth() {
        return birth;
    }

    public void setBirth(String birth) {
        this.birth = birth;
    }

    public Date getEnterDate() {
        return enterDate;
    }

    public void setEnterDate(Date enterDate) {
        this.enterDate = enterDate;
    }

    public int getAccount() {
        return account;
    }

    public void setAccount(int account) {
        this.account = account;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "[고객ID] " + id  +
                " [이름] " + name +
                " [생년월일] " + birth +
                " [가입날짜] " + enterDate +
                " [계좌번호] " + account +
                " [잔액] " + balance +"원";
    }

    public Member(String name, String birth){
        this.name = name;
        this.birth = birth;
    }
}

도메인을 다룰 데이터 계층을 생성했습니다.
은행에 고객이 가입 시, 이름과 생년월일만을 받도록 했기 때문에 이름과 생년월일 받아 객체를 생성하는 생성자를 두었습니다.
나머지 요소들을 가입과 동시에 주입받도록 했습니다.

Controller

MainAction

package Controller;

public enum MainAction {
    JOIN_BANK("은행가입"),
    SAVINGS("예적금"),
    SHOW_ALL_ACCOUNTS("전체계좌조회"),
    BALANCE("잔액조회"),
    EXIT("나가기");

    private final String action;

    MainAction(String action) {
        this.action = action;
    }
    public String getAction() {
        return action;
    }
    public static MainAction findByAction(String action) {
        for (MainAction value : values()) {
            if (value.getAction().equalsIgnoreCase(action)) {
                return value;
            }
        }
        return null;
    }
}

enum클래스를 활용하여 사용자가 은행 업무명을 잘못 입력하였을 경우의 예외처리를 하고자 했습니다.

SavingAction

package Controller;

public enum SavingAction {
    TRANSFER("계좌이체"),
    DEPOSIT("입금"),
    WITHDRAW("출금");
    private final String action;

    SavingAction(String action) {
        this.action = action;
    }
    public String getAction() {
        return action;
    }
    public static SavingAction findByAction(String action) {
        for (SavingAction value : values()) {
            if (value.getAction().equalsIgnoreCase(action)) {
                return value;
            }
        }
        return null;
    }
}

메인 액션인 예적금에는 '입금', '출금', '계좌이체' 이렇게 3가지의 세부 행동이 파생되므로 별도로 enum클래스를 만들어주었습니다.

BankController

package Controller;

import Service.BankService;
import Service.BankServiceImpl;

import java.util.Scanner;

import static java.lang.System.exit;

public class BankController {
    public void entranceBank(){
        BankService bankService = new BankServiceImpl();
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.println();
            System.out.println("원하시는 업무를 입력해주세요.");
            System.out.println("[은행가입] [예적금] [잔액조회] [전체계좌조회] [나가기]");
            String userInput = sc.nextLine().trim();
            MainAction action = MainAction.findByAction(userInput);
            if (action == null) {
                System.out.println("!!!올바른 업무를 입력해주세요!!!");
                continue;
            }
            switch (action) {
                case JOIN_BANK ->{
                    System.out.print("이름을 입력해주세요 : ");
                    String name = sc.nextLine();;
                    System.out.print("주민번호 앞자리를 입력해주세요 : ");
                    String birth = sc.nextLine();
                    bankService.join(name, birth);
                }
                case SAVINGS -> {
                    System.out.println("원하시는 업무를 입력해주세요.");
                    System.out.println("[계좌이체] [입금] [출금]");
                    userInput = sc.nextLine();
                    SavingAction savingAction = SavingAction.findByAction(userInput);
                    if (savingAction == null) {
                        System.out.println("!!!올바른 업무를 입력해주세요!!!");
                        continue;
                    }
                    switch (savingAction){
                        case TRANSFER->{
                            //todo: 계좌이체 서비스
                            System.out.print("고객님의 이름을 입력해주세요: ");
                            String name = sc.nextLine();
                            System.out.print("고객님의 생년월일을 입력해주세요: ");
                            String birth = sc.nextLine();
                            if(bankService.checkMember(name, birth)){
                                System.out.print("송금할 계좌번호를 입력하세요: ");
                                int account = Integer.parseInt(sc.nextLine());
                                System.out.print("송금할 금액을 입력하세요: ");
                                int money = Integer.parseInt(sc.nextLine());
                                bankService.transfer(name, birth, money, account);
                            }
                        }
                        case DEPOSIT ->{
                            System.out.print("입금할 계좌번호를 입력하세요: ");
                            int account = Integer.parseInt(sc.nextLine());
                            System.out.print("입금할 금액을 입력하세요: ");
                            int money = Integer.parseInt(sc.nextLine());
                            bankService.deposit(money, account);
                        }
                        case WITHDRAW ->{
                            System.out.print("출금할 계좌번호를 입력하세요: ");
                            int account = Integer.parseInt(sc.nextLine());
                            System.out.print("출금할 금액을 입력하세요: ");
                            int money = Integer.parseInt(sc.nextLine());
                            bankService.withdraw(money, account);
                        }
                    }
                }
                case SHOW_ALL_ACCOUNTS -> {
                    bankService.showAll();
                }
                case BALANCE -> {
                    System.out.print("고객님의 이름을 입력해주세요: ");
                    String name = sc.nextLine();
                    System.out.print("고객님의 생년월일을 입력해주세요: ");
                    String birth = sc.nextLine();
                    bankService.showBalance(name, birth);
                }
                case EXIT ->{
                    System.out.println("조심히 가십쇼.");
                    exit(0);
                }
            }
        }
    }
}

컨트롤러 계층에서는 해당 업무에 맞는 사용자의 입력만을 받고 서비스 계층으로 구체적 행동을 위임해주도록 했습니다.
때문에 컨트롤러에서는 사용자 입력에 대한 예외처리만 해두었고, 서비스 객체를 생성하여 사용자의 입력을 서비스 계층에 넘겨주도록 했습니다.

Repository

BankRepository

package Repository;

import Data.Member;

import java.util.List;
import java.util.Optional;

public interface BankRepository {
    Member join(Member member);
    List<Member> findAll();
    Optional<Member> findByAccount(int account);
    Optional<Member> findMember(String name, String birth);
    int deposit(Member member, int money);
    void withdraw(Member member, int money);
}

데이터를 건드릴 레포지토리 계층에 은행 업무에 필요한 레포지토리의 역할을 명시한 인터페이스를 만들었습니다.

MemoryBankRepository

package Repository;
import Data.Member;
import java.util.*;

public class MemoryBankRepository  implements BankRepository{
    private static HashMap<Integer, Member> bank = new HashMap<>();
    private static Long id = 0L;

    @Override
    public Member join(Member member) {
        member.setId(id++);
        member.setEnterDate(new Date());
        member.setAccount((int)(Math.random()*10000000));
        bank.put(member.getAccount(), member);
        return member;
    }
    @Override
    public List<Member> findAll(){
        return new ArrayList<>(bank.values());
    }

    @Override
    public Optional<Member> findByAccount(int account) {
        return Optional.ofNullable(bank.get(account));
    }


    @Override
    public Optional<Member> findMember(String name, String birth) {
        Optional<Member> member = bank.values().stream()
                .filter(m -> m.getName().equals(name) && m.getBirth().equals(birth))
                .findAny();
        return member;
    }

    @Override
    public int deposit(Member member, int money) {
        member.setBalance(member.getBalance()+money);
        return member.getBalance();
    }

    @Override
    public void withdraw(Member member, int money) {
        member.setBalance(member.getBalance()-money);
    }
}

현재는 따로 데이터베이스를 사용하지 않으므로 Memory라는 키워드를 붙혀 구현 클래스를 만들었습니다.
해쉬맵을 이용하여 계좌번호를 키값으로 가입한 멤버 객체들을 관리하고자 했습니다.

Service

BankService

package Service;

public interface BankService {
    void join(String name, String birth);
    void deposit(int money, int account);
    void withdraw(int money, int account);
    void showAll();
    void showBalance(String name, String birth);
    boolean checkMember(String name, String birth);
    void transfer(String name, String birth, int money, int account);
}

은행 서비스에 필요한 업무들을 명시한 인터페이스를 만들었습니다.

BankServiceImpl

package Service;

import Data.Member;
import Repository.MemoryBankRepository;

import java.util.List;
import java.util.Optional;
import java.util.TreeMap;

public class BankServiceImpl implements BankService{
    MemoryBankRepository memoryBankRepository = new MemoryBankRepository();
    @Override
    public void join(String name, String birth) {
        Member member = new Member(name, birth.trim());
        Optional<Member> already = memoryBankRepository.findMember(name, birth);
        if (already.isEmpty()) {
            memoryBankRepository.join(member);
            System.out.println("성공적으로 가입되었습니다!");
            System.out.println("가입정보: "+ member);
        }
        else{
            System.out.println("이미 가입되어 있는 고객입니다.");
        }
    }

    @Override
    public void deposit(int money, int account){
        Optional<Member> member = memoryBankRepository.findByAccount(account);
        if (memoryBankRepository.findByAccount(account).isEmpty()) {
            System.out.println("존재하지 않는 계좌번호입니다.");
        }else {
            int afterBalance = memoryBankRepository.deposit(member.get(), money);
            System.out.println(afterBalance+"원이 성공적으로 입금되었습니다. 잔액: "+member.get().getBalance()+"원");
        }
    }

    @Override
    public void withdraw(int money, int account) {
        Optional<Member> member = memoryBankRepository.findByAccount(account);
        if (memoryBankRepository.findByAccount(account).isEmpty()) {
            System.out.println("존재하지 않는 계좌번호입니다.");
        }else {
            if (money > member.get().getBalance()){
                System.out.println("잔액 이상의 금액은 출금할 수 없습니다. 잔액: "+member.get().getBalance()+"원");
            }else {
                memoryBankRepository.withdraw(member.get(), money);
                System.out.println(money+"원이 성공적으로 출금되었습니다. 잔액: "+member.get().getBalance()+"원");
            }
        }
    }

    @Override
    public void showAll() {
        List<Member> members = memoryBankRepository.findAll();
        if ((long) members.size() == 0) {
            System.out.println("아직 조회할 계좌 정보가 없습니다.");
        }
        for (Member member : members) {
            System.out.println(member);
        }
    }

    @Override
    public void showBalance(String name, String birth) {
        Optional<Member> member = memoryBankRepository.findMember(name, birth.trim());
        if (member.isEmpty()) {
            System.out.println("계좌 정보가 존재하지 않습니다.");
        }else{
            System.out.println(member.get().getName()+"님의 잔액은 "+member.get().getBalance()+"원 입니다.");
        }
    }

    @Override
    public boolean checkMember(String name, String birth) {
        Optional<Member> member = memoryBankRepository.findMember(name, birth);
        if (member.isEmpty()) {
            System.out.println("고객 정보가 일치하지 않습니다.");
            return false;
        }else{
            return true;
        }
    }

    @Override
    public void transfer(String name, String birth, int money, int account) {
        Optional<Member> toMember = memoryBankRepository.findByAccount(account);
        if (toMember.isEmpty()) {
            System.out.println("존재하지 않는 계좌번호입니다.");
        }else {
            Optional<Member> fromMember = memoryBankRepository.findMember(name, birth);
            if (money > fromMember.get().getBalance()){
                System.out.println("잔액이 부족합니다. 잔액: "+fromMember.get().getBalance()+"원");
            }else{
                toMember.get().setBalance(toMember.get().getBalance()+money);
                fromMember.get().setBalance(fromMember.get().getBalance()-money);
                System.out.println("성공적으로 이체되었습니다.");
            }
        }
    }
}

가입

사용자로부터 입력받은 이름과 생년월일을 바탕으로 은행 레포지토리에서 받아온 Optional를 이용했습니다.
만약 기존에 이름과 생년월일이 동일한 사용자가 있는지 isEmpty() 메서드를 이용하여 예외처리 했습니다.

잔액조회

마찬가지로 이름과 생년월일을 통해 Optional를 받아온 후, 만약 존재한다면 잔액을 출력해주었습니다.

전체계좌조회

은행 레포지토리를 통해 받아온 Member 리스트를 순회하며 모든 멤버 정보를 출력해주었습니다.

입금, 출금, 계좌이체

이전 서비스들과 동일하게 사용자 정보를 레포지토리를 통해 Optional 객체를 받아온 후,
Member 클래스의 set과 get을 통해 적절한 행동을 취해주었습니다.

고민한 부분들

  1. 은행 도메인을 별도로 관리하는게 더 좋았을까요?
  2. 예외처리를 단순 조건문과 출력으로 다뤄주었는데, 별도의 예외 클래스를 만드는게 나았을까요?
  3. Optional을 통한 예외처리보다 더 좋은 방법이 있을까요?
  4. 출력을 담당하는 기능들을 따로 클래스로 만드는게 나았을까요?
  5. 컨트롤러에서 일부 예외처리를 해주는데, 단순 입력만 받고 예외처리와 같은 로직은 전부 서비스계층에 주는게 나았을까요?

@seokbeom00 seokbeom00 requested a review from sohyundoh April 10, 2024 10:12
@geniusYoo
Copy link

전체적으로 인터페이스와 구현체를 적절하게 사용하신 것 같습니다 !!

개인적인 의견을 하나 남겨보자면 🙌
BankService 인터페이스 안에 은행 업무에 필요한 모든 기능을 모아둔다면, 기능을 확장할 때 BankService 인터페이스를 계속 수정해야할 것 같아요!
그 대신 모든 은행 업무에서 대체적으로 필요한 것들을 공통적인 인터페이스로 묶어주고, 각 업무에서 필요한 기능들만 구현한 구체적인 인터페이스를 추가로 구현하면 ISP, OCP 원칙에 조금 더 부합할 수 있을 것 같습니다 :)

어디까지나 지극히 저의 의견이므로 참고만 해주세요 !! 코드 잘 봤습니다 👍🏻👍🏻

@minwoo0419
Copy link

인터페이스와 구현체를 이용해서 잘 구현하신 것 같아요!고민하신 부분들도 모두 괜찮은 의견이신 것 같습니다!! 잘 보고 갑니다!

Copy link
Contributor

@sohyundoh sohyundoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

과제하느라 고생하셨습니다!

Repository로 상위 인터페이스를 두고 구현한 점이 아주 인상깊네요!
질문하셨던 요소 중에 Optional 예외 처리 방법이었는데요!

예외 상황은 여러가지가 발생할 수 있고 Optional의 경우 Null의 객체일 경우 orElseThrow를 할 수 있게 하고 있죠! 요런 예외처리는 Optional 내부에 있는 함수를 적극 활용하는 것이 좋다고 생각합니다. 이외의 예외 상황의 경우에는 분기처리문(if else)이 불가피하긴 합니다.. 하지만, 이런 예외의 경우 모듈화(메서드로 분리)하고 이 후에 또 사용해야하는 상황에서 재사용을 하는 방법으로 코드 재사용성을 높입니다! 저도 이 외로 더 좋은 방법이 있는지 찾아봐야겠네요!

고생하셨습니다 -!

@eeddiinn
Copy link

과제하느라 고생하셨습니다 !!!!! 코드 잘 보고 갑니당 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants